|
1
|
|
|
/** |
|
2
|
|
|
* @module http/rest |
|
3
|
|
|
*/ |
|
4
|
|
|
|
|
5
|
|
|
var C = require('./_common') |
|
6
|
|
|
var M = C.Method |
|
7
|
|
|
var Client = require('./basic').Client |
|
8
|
|
|
var Slf4j = require('../logger').Slf4j |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* @interface Serializer |
|
12
|
|
|
*/ |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* @function Serializer.serialize |
|
16
|
|
|
* |
|
17
|
|
|
* @param {object} |
|
18
|
|
|
* |
|
19
|
|
|
* @return {string} |
|
20
|
|
|
*/ |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* @function Serializer.deserialize |
|
24
|
|
|
* |
|
25
|
|
|
* @param {string} |
|
26
|
|
|
* |
|
27
|
|
|
* @return {object} |
|
28
|
|
|
*/ |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @class |
|
32
|
|
|
* |
|
33
|
|
|
* @implements Serializer |
|
34
|
|
|
*/ |
|
35
|
|
|
function JsonSerializer () { |
|
36
|
|
|
this.serialize = JSON.stringify |
|
37
|
|
|
this.deserialize = JSON.parse |
|
38
|
|
|
} |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* @class RestClientSettings |
|
42
|
|
|
* |
|
43
|
|
|
* @property {string|undefined} url String to prepend to every request route. |
|
44
|
|
|
* @property {Serializer|undefined} serializer Serializer to encode/decode |
|
45
|
|
|
* messages, default: JsonSerializer |
|
46
|
|
|
* @property {number|undefined} retries Maximum number of retries for each |
|
47
|
|
|
* request, default: `4` |
|
48
|
|
|
* @property {string|undefined} methodOverrideHeader Name of header that should |
|
49
|
|
|
* conceal real request HTTP method, |
|
50
|
|
|
* look up `X-HTTP-Method-Override` in nearest search engine. Default: none |
|
51
|
|
|
* @property {boolean|undefined} retryOnNetworkError Whether request should be |
|
52
|
|
|
* retried on connection error, |
|
53
|
|
|
* default: `true` |
|
54
|
|
|
* @property {boolean|undefined} retryOnServerError Whether request should be |
|
55
|
|
|
* retried on server error (5xx), |
|
56
|
|
|
* default: `true` |
|
57
|
|
|
* @property {HeaderBag|undefined} headers Object containing headers for every |
|
58
|
|
|
* emitted request, default: `{}` |
|
59
|
|
|
* @property {int|undefined} timeout Default request timeout in milliseconds, |
|
60
|
|
|
* may be overriden on per-request level. Falsey values will use no timeout |
|
61
|
|
|
* at all. |
|
62
|
|
|
* @property {LoggerOptions} logger Logger options. |
|
63
|
|
|
* @property {IHttpClient} client Underlying http client |
|
64
|
|
|
*/ |
|
65
|
|
|
|
|
66
|
|
|
// noinspection JSClosureCompilerSyntax |
|
67
|
|
|
/** |
|
68
|
|
|
* @class |
|
69
|
|
|
* |
|
70
|
|
|
* @implements IRestClient |
|
71
|
|
|
* |
|
72
|
|
|
* @param {RestClientSettings|Object} [settings] |
|
73
|
|
|
* @param {netHttpRequestAsync} [transport] |
|
74
|
|
|
*/ |
|
75
|
|
|
function RestClient (settings, transport) { |
|
76
|
|
|
var self = this |
|
77
|
|
|
var opts = getDefaults() |
|
78
|
|
|
var client |
|
79
|
|
|
var logger |
|
80
|
|
|
var counter = 0 |
|
81
|
|
|
|
|
82
|
|
|
if (settings instanceof Function) { |
|
83
|
|
|
transport = settings |
|
84
|
|
|
settings = {} |
|
85
|
|
|
} |
|
86
|
|
|
settings = settings || {} |
|
87
|
|
|
if (typeof settings === 'string' || settings instanceof String) { |
|
88
|
|
|
settings = {url: settings} |
|
89
|
|
|
} |
|
90
|
|
|
Object.keys(opts).forEach(function (key) { |
|
91
|
|
|
if (key in settings) { |
|
92
|
|
|
opts[key] = settings[key] |
|
93
|
|
|
} |
|
94
|
|
|
}) |
|
95
|
|
|
client = settings.client || new Client(opts, transport) |
|
96
|
|
|
logger = Slf4j.factory(opts.logger, 'ama-team.voxengine-sdk.http.rest') |
|
97
|
|
|
|
|
98
|
|
|
function execute (request) { |
|
99
|
|
|
var id = ++counter |
|
100
|
|
|
request.id = request.id || id |
|
101
|
|
|
var basicRequest = { |
|
102
|
|
|
url: request.resource || request.route, // 0.2.0 compatibility |
|
103
|
|
|
method: request.method, |
|
104
|
|
|
query: request.query, |
|
105
|
|
|
headers: request.headers, |
|
106
|
|
|
timeout: typeof request.timeout === 'number' ? request.timeout : settings.timeout |
|
107
|
|
|
} |
|
108
|
|
|
logger.debug('Executing request #{} `{} {}`', request.id, request.method, request.resource) |
|
109
|
|
|
basicRequest.payload = request.payload ? opts.serializer.serialize(request.payload) : null |
|
110
|
|
|
return client |
|
111
|
|
|
.execute(basicRequest) |
|
112
|
|
|
.then(function (response) { |
|
113
|
|
|
logger.debug('Request #{} `{} {}` received response with code {}', |
|
114
|
|
|
request.id, request.method, request.resource, response.code) |
|
115
|
|
|
return { |
|
116
|
|
|
code: response.code, |
|
117
|
|
|
payload: response.payload ? opts.serializer.deserialize(response.payload) : null, |
|
118
|
|
|
headers: response.headers, |
|
119
|
|
|
request: request |
|
120
|
|
|
} |
|
121
|
|
|
}, function (e) { |
|
122
|
|
|
logger.debug('Request #{} `{} {}` has finished with error {}', |
|
123
|
|
|
request.id, request.method, request.resource, e.name) |
|
124
|
|
|
throw e |
|
125
|
|
|
}) |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
/** |
|
129
|
|
|
* @inheritDoc |
|
130
|
|
|
*/ |
|
131
|
|
|
function request (method, resource, payload, query, headers, timeout) { |
|
132
|
|
|
return execute({resource: resource, method: method, query: query, payload: payload, headers: headers, timeout: timeout}) |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
/** |
|
136
|
|
|
* @inheritDoc |
|
137
|
|
|
*/ |
|
138
|
|
|
this.execute = execute |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* @inheritDoc |
|
142
|
|
|
*/ |
|
143
|
|
|
this.request = request |
|
144
|
|
|
|
|
145
|
|
|
/** |
|
146
|
|
|
* @inheritDoc |
|
147
|
|
|
*/ |
|
148
|
|
|
this.exists = function (resource, query, headers, timeout) { |
|
149
|
|
|
return request(M.Head, resource, null, query, headers, timeout) |
|
150
|
|
|
.then(function (response) { |
|
151
|
|
|
return response.code !== 404 |
|
152
|
|
|
}) |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/** |
|
156
|
|
|
* @inheritDoc |
|
157
|
|
|
*/ |
|
158
|
|
|
this.get = function (resource, query, headers, timeout) { |
|
159
|
|
|
return request(M.Get, resource, null, query, headers, timeout) |
|
160
|
|
|
.then(function (response) { |
|
161
|
|
|
return response.code === 404 ? null : response.payload |
|
162
|
|
|
}) |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
var methods = {create: M.Post, set: M.Put, modify: M.Patch, delete: M.Delete} |
|
166
|
|
|
Object.keys(methods).forEach(function (method) { |
|
167
|
|
|
self[method] = function (resource, payload, headers, query, timeout) { |
|
168
|
|
|
return request(methods[method], resource, payload, query, headers, timeout) |
|
169
|
|
|
.then(function (response) { |
|
170
|
|
|
if (response.code === 404) { |
|
171
|
|
|
var message = 'Modification request has returned 404 status code' |
|
172
|
|
|
throw new C.NotFoundException(message, response.request, response) |
|
173
|
|
|
} |
|
174
|
|
|
return response.payload |
|
175
|
|
|
}) |
|
176
|
|
|
} |
|
177
|
|
|
}) |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** |
|
181
|
|
|
* @return {RestClientSettings} |
|
182
|
|
|
*/ |
|
183
|
|
|
function getDefaults () { |
|
184
|
|
|
return { |
|
185
|
|
|
url: '', |
|
186
|
|
|
client: null, |
|
187
|
|
|
serializer: new JsonSerializer(), |
|
188
|
|
|
retries: 4, |
|
189
|
|
|
methodOverrideHeader: null, |
|
190
|
|
|
retryOnServerError: true, |
|
191
|
|
|
retryOnNetworkError: true, |
|
192
|
|
|
headers: {}, |
|
193
|
|
|
logger: {} |
|
194
|
|
|
} |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
RestClient.getDefaults = getDefaults |
|
198
|
|
|
|
|
199
|
|
|
module.exports = { |
|
200
|
|
|
Client: RestClient, |
|
201
|
|
|
/** @deprecated */ |
|
202
|
|
|
getDefaults: getDefaults |
|
203
|
|
|
} |
|
204
|
|
|
|